Skip to content

Conversation

@mtrezza
Copy link
Member

@mtrezza mtrezza commented Nov 26, 2025

New Pull Request Checklist

Issue Description

Currently, the info panel can only show 1 object at a time. That is inefficient when traversing many objects.

Approach

Add displaying multiple info panels in parallel and batch navigation to quickly traverse batches of objects.

Added new menu options:

  • Sync panel scrolling: scrolls all panels in sync for faster data review
  • Batch-navigate panels: pressing the up/down keyboard arrow keys jumps the batch of currently visible objects in info panels
  • Show panel selection: adds checkboxes to the top of each info panel for quick selection via mouse cursor

TODOs before merging

  • Add changes to documentation (guides, repository pages, in-code descriptions)

Summary by CodeRabbit

  • New Features

    • Multi-panel data browsing: view multiple data panels side-by-side
    • Add and Remove panel controls for flexible panel management
    • Panel synchronization settings: synchronized scrolling, batch navigation, and panel selection checkboxes
  • Documentation

    • Updated prefetching documentation to clarify behavior in multi-panel mode
  • UI/Style

    • Enhanced toolbar button styling with improved visual states and disabled feedback

✏️ Tip: You can customize this high-level summary in your review settings.

@parse-github-assistant
Copy link

parse-github-assistant bot commented Nov 26, 2025

🚀 Thanks for opening this pull request! We appreciate your effort in improving the project. Please let us know once your pull request is ready for review.

@coderabbitai
Copy link

coderabbitai bot commented Nov 26, 2025

📝 Walkthrough

Walkthrough

Adds multi-panel support to the data browser: panel management UI (Add/Remove panels, Show/Hide), synchronized scrolling and wheel handling, batch navigation with adjusted prefetch semantics, per-panel data state/fetching, localStorage persistence for panel settings, and supporting UI/CSS updates. README prefetch wording clarified.

Changes

Cohort / File(s) Summary
Documentation
README.md
Clarified infoPanel[*].prefetchObjects meaning to "navigation steps to prefetch ahead" and noted that in multi-panel + batch navigation mode total prefetched objects = prefetchObjects × panelCount.
Toolbar UI
src/components/Toolbar/Toolbar.react.js, src/components/Toolbar/Toolbar.scss
Replaced single toggle with a .panelButtons container exposing Add/Remove Panel actions and a dedicated Show/Hide toggle; Remove disabled when panelCount ≤ 1. Adjusted button spacing, nowrap, and disabled styling.
BrowserToolbar Integration
src/dashboard/Data/Browser/BrowserToolbar.react.js
Added props addPanel, removePanel, panelCount and passed them to Toolbar. Added three Settings menu items: Sync Panel Scrolling, Batch Navigate Panels, Show Panel Selection.
DataBrowser Core Logic
src/dashboard/Data/Browser/DataBrowser.react.js
Added multi-panel state (e.g., panelCount, multiPanelData, displayedObjectIds, syncPanelScroll, batchNavigate, showPanelCheckbox), localStorage keys, methods (addPanel, removePanel, toggleSyncPanelScroll, toggleBatchNavigate, toggleShowPanelCheckbox, handlePanelScroll, handleWrapperWheel, fetchDataForMultiPanel), multi-panel rendering, per-panel fetch/prefetch integration, and wheel/scroll event lifecycle handling.
Multi-Panel Styling
src/dashboard/Data/Browser/Databrowser.scss
Added .multiPanelWrapper, .panelColumn, .panelSeparator, and .panelHeader for horizontal multi-panel layout, scrollbars, separators, and sticky headers with centered checkbox.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    participant User
    participant Toolbar
    participant BrowserToolbar
    participant DataBrowser
    participant MultiPanelUI

    User->>Toolbar: Click "Add Panel"
    Toolbar->>BrowserToolbar: (prop) addPanel()
    BrowserToolbar->>DataBrowser: addPanel()
    DataBrowser->>DataBrowser: panelCount++\ninitialize multiPanelData entry\npersist settings
    DataBrowser->>MultiPanelUI: render additional panel column

    User->>MultiPanelUI: Scroll a panel
    MultiPanelUI->>DataBrowser: handlePanelScroll(event, index)
    alt syncPanelScroll enabled
        DataBrowser->>MultiPanelUI: update scroll positions for all panels
    end

    User->>MultiPanelUI: Wheel scroll (batch mode)
    MultiPanelUI->>DataBrowser: handleWrapperWheel(event)
    alt batchNavigate enabled
        DataBrowser->>DataBrowser: apply delta across panels\nfetchDataForMultiPanel() -> trigger prefetch
        Note right of DataBrowser: Prefetch count = prefetchObjects × panelCount
    end

    User->>Toolbar: Click "Remove Panel"
    Toolbar->>BrowserToolbar: (prop) removePanel()
    BrowserToolbar->>DataBrowser: removePanel()
    DataBrowser->>DataBrowser: panelCount--\ncleanup multiPanelData entry
    DataBrowser->>MultiPanelUI: re-render panels
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

  • Pay attention to DataBrowser.react.js additions: multiPanel state lifecycle, fetch/prefetch logic, event listener binding/unbinding, and localStorage reads/writes.
  • Verify Toolbar.react.js conditional rendering and disabled state for Remove Panel.
  • Confirm BrowserToolbar.react.js prop threading and new Settings menu items correctly call DataBrowser toggles.

Possibly related PRs

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the main changes: adding multiple info panel display and batch navigation support for efficient object traversal.
Description check ✅ Passed The description includes issue context, approach explanation, and completed documentation updates. Checklist items are marked complete where applicable.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@parseplatformorg
Copy link
Contributor

parseplatformorg commented Nov 26, 2025

Snyk checks have passed. No issues have been found so far.

Status Scanner Critical High Medium Low Total (0)
Open Source Security 0 0 0 0 0 issues

💻 Catch issues earlier using the plugins for VS Code, JetBrains IDEs, Visual Studio, and Eclipse.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (6)
src/dashboard/Data/Browser/Databrowser.scss (1)

27-90: Consider keyboard/a11y for panel header checkbox

panelHeader is clickable while the nested input[type="checkbox"] is pointer-events: none, and the header div itself is not focusable. That means keyboard users can’t toggle selection easily, and screen readers may expose a checkbox that doesn’t respond to direct interaction. Consider either:

  • Making the checkbox itself the interactive control (no pointer-events: none, with proper onChange wiring), or
  • Adding tabindex={0} and keyboard handlers (Enter/Space) to the header while treating the checkbox as purely decorative (aria-hidden="true").

This would keep the current UX while improving accessibility.

src/components/Toolbar/Toolbar.react.js (1)

161-188: Add PropTypes for new panel management props

The Add/Remove/Show/Hide panel controls are wired correctly and gated on classwiseCloudFunctions, but the new props (addPanel, removePanel, panelCount, togglePanel, isPanelVisible) aren’t reflected in Toolbar.propTypes. Adding them (with the appropriate function/number/bool types) would make misuse easier to catch.

src/dashboard/Data/Browser/DataBrowser.react.js (4)

80-136: Multi-panel state, persistence, and selection handling – behavior check and cleanup

A few points around the new state/persistence wiring:

  1. New localStorage-backed flags default to “on”

    • storedSyncPanelScroll, storedBatchNavigate, and storedShowPanelCheckbox are initialized with getItem(...) !== 'false', so the features are enabled by default even when no key exists yet. Toggling stores 'true' / 'false'.
    • This effectively turns these features on for existing users after upgrade. If that’s intentional, this is fine; if you’d prefer opt-in behavior, consider switching the reads to === 'true' instead.
  2. setSelectedObjectId multi-panel batch logic

    • The batch calculation around displayedObjectIds and _objectsToFetch looks sound (selected row anchored at start of batch, pulling up to panelCount objects, using cache when fresh).
    • Near the end of the dataset, panelCount can exceed displayedObjectIds.length (e.g. adding a panel when there aren’t enough remaining rows). That’s harmless but means batchNavigate and prefetch will step by panelCount even though fewer panels are actually rendered. If you want UI and behavior to stay in lockstep, you could clamp the effective step size to displayedObjectIds.length.
  3. Minor style cleanup from static analysis

    • Inside setSelectedObjectId, newMultiPanelData is never reassigned, only mutated, so it can be a const:
  •      let newMultiPanelData = { ...prevState.multiPanelData };
    
  •      const newMultiPanelData = { ...prevState.multiPanelData };
    
    
    

Overall the selection and state wiring look correct; the above are mainly behavioral choices and small polish items.

Also applies to: 159-171, 776-848, 886-908


229-245: Scroll synchronization: avoid potential scroll churn and clarify scroll target

The scroll-sync plumbing generally works, but there are a couple of edge cases to consider:

  1. Possible scroll event ping-pong between panels

    • handlePanelScroll copies event.target.scrollTop to all other panelColumnRefs. Those programmatic updates will in turn fire onScroll on the other columns, which then write back to the origin column. Most browsers suppress redundant events when the value is unchanged, but this isn’t guaranteed and can cause extra scroll events.
    • You can guard against this cheaply by only writing when the delta is non-zero, e.g.:
    const scrollTop = event.target.scrollTop;
    this.panelColumnRefs.forEach((ref, i) => {
      if (i !== index && ref?.current && ref.current.scrollTop !== scrollTop) {
        ref.current.scrollTop = scrollTop;
      }
    });
  2. Nested scrolling vs. “Scroll to top” behavior

    • The container aggregationPanelRef still has overflow: auto, while each .panelColumn also has overflow-y: auto. In multi-panel mode, most of the vertical scrolling will happen inside columns, but the “Scroll to top” setting only resets this.aggregationPanelRef.current.scrollTop. Users can end up with columns scrolled independently even after an auto-reset.
    • If you want “Scroll to top” to fully reset multi-panel layout, consider also resetting each panelColumnRef.current.scrollTop when that flag is enabled.

The current implementation is functional; these changes would make scroll behavior more predictable and robust.

Also applies to: 297-324, 910-939


941-990: Reduce duplicate Cloud function calls between multi-panel fetch and prefetch

fetchDataForMultiPanel and prefetchObject both call Parse.Cloud.run with the same cloudCodeFunction and object pointer, but via different paths:

  • setSelectedObjectId collects _objectsToFetch and calls fetchDataForMultiPanel for each (current batch).
  • handlePrefetch detects a navigation pattern and calls prefetchObject for upcoming objects (future batches).

Because handlePrefetch() is invoked at the end of setSelectedObjectId’s callback, you can end up with both functions firing separate network requests for the same object (especially in multi-panel mode where upcoming batch rows overlap with displayedObjectIds).

To avoid redundant Cloud calls and reduce load:

  • Consider having fetchDataForMultiPanel(objectId) delegate to prefetchObject(objectId) and only augment multiPanelData from the existing prefetchCache, or
  • Extract a shared “fetch once and update both caches” helper that both prefetchObject and fetchDataForMultiPanel use.

That would ensure each object’s panel data is fetched at most once per staleness window while keeping the current caching and media-prefetch behavior.

Also applies to: 1095-1127, 1231-1257


1414-1486: Multi-panel rendering: loading UX and selection/checkbox accessibility

The multi-panel rendering block is a nice composition, but there are a few UX/a11y details worth tightening:

  1. Loading state for secondary panels

    • Panels for displayedObjectIds other than the currently selected row derive panelData from multiPanelData[objectId] || {} and use isLoading = objectId === selectedObjectId && isLoadingCloudFunction.
    • When fetchDataForMultiPanel is fetching data for non-selected objects, those panels render with empty data and showAggregatedData={true}, which leads AggregationPanel to show “No object selected.” rather than a loading indicator.
    • If you want to signal that data is on the way, consider tracking a per-object loading flag (or checking presence in _objectsToFetch) and passing isLoadingCloudFunction accordingly so secondary panels show a spinner instead of “No object selected.” while fetching.
  2. Panel header checkbox interaction

    • The header div has onClick toggling selectRow(objectId, !isRowSelected), while the nested <input type="checkbox"> is readOnly and rendered purely as a visual indicator. Combined with pointer-events: none in the SCSS, the checkbox isn’t directly interactive, and the header div isn’t keyboard-focusable.
    • For better accessibility, consider:
      • Making the checkbox itself the interactive element (handling onChange and letting it focus), or
      • Treating the checkbox as decorative (aria-hidden) and adding tabIndex={0} + key handlers on the header so keyboard users can toggle selection.
  3. Scroll-to-top vs multi-panel columns

    • As noted earlier, the “Scroll to top” behavior still only touches aggregationPanelRef.scrollTop. If you decide that setting should also reset per-panel scrolls, this render block is the right place to apply those resets via the panelColumnRefs.

Behavior is otherwise coherent: the correct error state is only passed to the currently selected object, and separators are rendered cleanly between panels.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3daecd8 and a5772aa.

📒 Files selected for processing (6)
  • README.md (1 hunks)
  • src/components/Toolbar/Toolbar.react.js (1 hunks)
  • src/components/Toolbar/Toolbar.scss (2 hunks)
  • src/dashboard/Data/Browser/BrowserToolbar.react.js (3 hunks)
  • src/dashboard/Data/Browser/DataBrowser.react.js (14 hunks)
  • src/dashboard/Data/Browser/Databrowser.scss (1 hunks)
🧰 Additional context used
🧠 Learnings (2)
📚 Learning: 2025-07-31T06:12:17.707Z
Learnt from: mtrezza
Repo: parse-community/parse-dashboard PR: 2957
File: src/dashboard/Data/Browser/BrowserTable.react.js:584-597
Timestamp: 2025-07-31T06:12:17.707Z
Learning: In Parse Dashboard's data browser selection logic (src/dashboard/Data/Browser/BrowserTable.react.js), the `selection['*']` pattern is used to handle global operations that pass `{ '*': true }` to indicate all items are selected, particularly for bulk operations like delete. This is not dead code but serves as compatibility layer for global operations that don't go through normal individual row selection workflows.

Applied to files:

  • src/dashboard/Data/Browser/DataBrowser.react.js
📚 Learning: 2025-05-27T12:09:47.644Z
Learnt from: mtrezza
Repo: parse-community/parse-dashboard PR: 2828
File: src/dashboard/Data/Browser/Browser.react.js:1605-1607
Timestamp: 2025-05-27T12:09:47.644Z
Learning: In script execution dialogs in Parse Dashboard (specifically the `confirmExecuteScriptRows` method in `src/dashboard/Data/Browser/Browser.react.js`), individual `setState` calls to update `processedScripts` counter should be kept as-is rather than batched, because this provides real-time progress feedback to users in the dialog UI.

Applied to files:

  • src/dashboard/Data/Browser/DataBrowser.react.js
🧬 Code graph analysis (2)
src/dashboard/Data/Browser/BrowserToolbar.react.js (3)
src/components/BrowserMenu/MenuItem.react.js (1)
  • MenuItem (11-58)
src/components/Icon/Icon.react.js (1)
  • Icon (12-29)
src/parse-interface-guide/ComponentsMap.js (2)
  • Icon (40-40)
  • Icon (40-40)
src/dashboard/Data/Browser/DataBrowser.react.js (1)
src/components/AggregationPanel/AggregationPanel.js (1)
  • AggregationPanel (15-223)
🪛 GitHub Check: Lint
src/dashboard/Data/Browser/DataBrowser.react.js

[failure] 791-791:
'newMultiPanelData' is never reassigned. Use 'const' instead

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Node 22
  • GitHub Check: Node 20
  • GitHub Check: Docker linux/amd64
🔇 Additional comments (3)
README.md (1)

1379-1385: Prefetching description aligns with multi-panel implementation

The updated prefetchObjects wording (navigation steps and prefetchObjects × panelCount in multi-panel mode) matches the current handlePrefetch logic in DataBrowser.react.js, so the docs look accurate.

src/components/Toolbar/Toolbar.scss (1)

129-171: Panel button styling looks consistent and scoped

The new .panelButtons container and .btn tweaks (padding, nowrap, disabled state) are cohesive with the toolbar layout and safely scoped via CSS modules; no issues spotted.

src/dashboard/Data/Browser/BrowserToolbar.react.js (1)

81-99: Multi-panel controls and Info Panel settings wiring look correct

The new panel-management props (addPanel, removePanel, panelCount) are correctly threaded from BrowserToolbar into Toolbar, and the three Info Panel settings (sync scroll, batch navigation, panel selection) follow the existing checked-menu-item pattern. No behavioral issues stand out here.

Also applies to: 284-300, 424-480

coderabbitai[bot]
coderabbitai bot previously approved these changes Nov 26, 2025
@mtrezza mtrezza merged commit 7ac3186 into parse-community:alpha Nov 26, 2025
10 of 11 checks passed
@mtrezza mtrezza deleted the feat/multi-preview branch November 26, 2025 21:09
parseplatformorg pushed a commit that referenced this pull request Nov 26, 2025
# [8.1.0-alpha.2](8.1.0-alpha.1...8.1.0-alpha.2) (2025-11-26)

### Features

* Add displaying multiple info panels and batch navigation ([#3020](#3020)) ([7ac3186](7ac3186))
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (6)
src/components/Toolbar/Toolbar.react.js (1)

161-189: Panel controls wiring looks solid; consider UX tweak for Add Panel

The visibility and enablement logic for Add/Remove/Show/Hide is consistent with panelCount and isPanelVisible, and matches the DataBrowser API. Only minor UX nit: addPanel is callable even when no row is selected, in which case it silently does nothing. If that’s a common case, consider disabling Add Panel (or showing a tooltip) until a row is selected so the behavior is more obvious.

src/dashboard/Data/Browser/DataBrowser.react.js (5)

789-848: Multi-panel batch selection and deferred fetch pipeline is well-structured

The extended setSelectedObjectId logic correctly:

  • Tracks selection history for prefetch heuristics.
  • Recomputes displayedObjectIds only when a new object falls outside the current multi-panel batch.
  • Reuses cached data where available and stages missing objects for later async fetch via _objectsToFetch.

Using a functional setState plus a post-update callback avoids race conditions. If you want to reduce state churn later, _objectsToFetch could be moved to an instance field, but that’s an optional micro-optimization.


886-908: Sync scroll and batch toggles behave correctly; scroll sync implementation is reasonable

The three toggle helpers correctly flip state and persist to localStorage, aligning with the default-on pattern used at initialization. Scroll syncing (handlePanelScroll and handleWrapperWheel) is straightforward and should work well in practice; any redundant scroll events will quickly converge without affecting correctness.

Also applies to: 910-939


941-990: Per-object multi-panel fetch is correct; consider DRY’ing with prefetchObject

fetchDataForMultiPanel correctly honors prefetch staleness, reuses cached entries, and updates both prefetchCache and multiPanelData on fresh fetches. It duplicates some logic from prefetchObject, so if this grows further it might be worth centralizing the Parse.Cloud.run + cache update logic in a shared helper.


992-1055: Panel add/remove logic is sound; only minor polish possible

addPanel correctly builds a contiguous batch starting at the selected row, hydrates multiPanelData from the live panel or cache, and defers missing fetches. removePanel cleanly decrements panelCount and trims displayedObjectIds.

Minor notes (optional):

  • When currentIndex is -1 (no valid selection), addPanel is a silent no-op; pairing this with a disabled Add Panel button or tooltip would make the UX clearer.
  • In removePanel, returning null instead of {} in the panelCount <= 1 case would avoid an unnecessary re-render.

1414-1486: Multi-panel rendering path is coherent and matches the data model

The multi-panel branch correctly:

  • Uses displayedObjectIds and multiPanelData to drive per-panel content.
  • Applies per-column scroll refs and onScroll handlers for sync behavior.
  • Integrates optional selection checkboxes and reuses the existing AggregationPanel API.

The single-panel fallback remains unchanged, so the feature is additive and backward-compatible. If in the future you need per-panel error reporting, you may want to model errorAggregatedData by objectId, but the current shared handling is acceptable.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a5772aa and 0def4a7.

📒 Files selected for processing (3)
  • src/components/Toolbar/Toolbar.react.js (1 hunks)
  • src/dashboard/Data/Browser/DataBrowser.react.js (14 hunks)
  • src/dashboard/Data/Browser/Databrowser.scss (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/dashboard/Data/Browser/Databrowser.scss
🧰 Additional context used
🧬 Code graph analysis (1)
src/dashboard/Data/Browser/DataBrowser.react.js (1)
src/components/AggregationPanel/AggregationPanel.js (1)
  • AggregationPanel (15-223)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Docker linux/amd64
🔇 Additional comments (6)
src/dashboard/Data/Browser/DataBrowser.react.js (6)

95-101: LocalStorage-backed defaults are consistent; default-on behavior is clear

The new sync scroll, batch navigation, and checkbox flags are correctly initialized from localStorage, with sensible default-on semantics via !== 'false'. The additional state for displayedObjectIds, panelCount, multiPanelData, and _objectsToFetch is coherent and sets up the multi-panel model cleanly.

Also applies to: 127-136


159-167: Method binding and new refs are consistent with existing patterns

Binding the new toggle/add/remove/scroll handlers in the constructor and introducing panelColumnRefs / multiPanelWrapperRef fit the existing class style and enable the required scroll syncing without surprises.

Also applies to: 169-171


635-637: Batch navigation step sizing for Up/Down looks correct

The use of stepSize = panelCount when batchNavigate is enabled (otherwise 1) is a clean way to move in batches. The Math.max / Math.min clamping correctly prevents out-of-bounds indices, and Ctrl/Meta behavior remains intact.

Also applies to: 679-685


1095-1127: Prefetch heuristic cleanly extended to multi-panel batches

The updated prefetch logic correctly infers a navigation step size from recent selections and prefetches ahead accordingly. Extending it to also prefetch the rest of each upcoming batch when panelCount > 1 is a good touch and should keep multi-panel views responsive without over-fetching.


1241-1247: Keeping multiPanelData in sync with cache hits is correct

Mirroring cached panel data into multiPanelData inside handleCallCloudFunction avoids redundant fetches when switching between single and multi-panel modes and ensures consistent data across both paths.


1519-1533: Toolbar wiring for multi-panel controls is consistent

Passing addPanel, removePanel, panelCount, and the three new toggle flags plus handlers into BrowserToolbar cleanly exposes the new behavior to the UI without altering existing props. This lines up with the updated Toolbar component.

@parseplatformorg
Copy link
Contributor

🎉 This change has been released in version 8.1.0-alpha.2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

state:released-alpha Released as alpha version

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants